package utils

import (
	"errors"
	"net"
	"regexp"
	"strings"
)

type AddrType byte

const (
	IP         AddrType = 0x00
	IPNet      AddrType = 0x01
	DomainName AddrType = 0x02 // 域名
	WildDomain AddrType = 0x03 // 通配符域名

	// 域名头的正则表达式
	DomainHeader = `^([0-9a-zA-Z.-]+\.)*`
)

var (
	domainReg      = regexp.MustCompile(`^(\*\.)?[0-9a-zA-Z.-]+[^.]$`) // 匹配完整域名和通配符域名
	waildDomainReg = regexp.MustCompile(`^(\*\.)[0-9a-zA-Z.-]+[^.]$`)  // 匹配通配符域名
)

// IPOrHost 用于匹配ip、网段或域名通配符的结构体
type IPOrHost struct {
	addrType AddrType       // ip 0 cidr 1  domain name 2
	addr     string         // 原始信息
	reg      *regexp.Regexp // 用于匹配域名
	ipnet    *net.IPNet     // 用于匹配网段
}

// NewIPOrHost 解析输入的str是否为IP地址、cidr或域名，如果都不是，则返回nil
func NewIPOrHost(str string) (*IPOrHost, error) {
	if strings.Contains(str, "/") {
		_, ipnet, err := net.ParseCIDR(str)
		if err != nil {
			// 字符串中包含 / ，但是又没有解析为cidr，必然错误
			return nil, err
		}
		result := new(IPOrHost)
		result.addrType = IPNet
		result.ipnet = ipnet
		result.addr = ipnet.String()
		return result, nil
	}
	ip := net.ParseIP(str)
	if ip != nil {
		result := new(IPOrHost)
		result.addrType = IP
		result.addr = ip.String()
		return result, nil
	}
	if !domainReg.MatchString(str) {
		return nil, errors.New("error domain format")
	}
	if waildDomainReg.MatchString(str) {
		// 通配符域名
		reg, err := regexp.Compile(strings.Replace(str, "*.", DomainHeader, 1))
		if err != nil {
			return nil, err
		}
		result := new(IPOrHost)
		result.addrType = WildDomain
		result.addr = str
		result.reg = reg
		return result, nil
	} else {
		result := new(IPOrHost)
		result.addrType = DomainName
		result.addr = str
		return result, nil
	}
}

// Match 判断输入的地址是否匹配，str是IP地址或域名字符串
func (h *IPOrHost) Match(str string) bool {
	if h.addrType == IP || h.addrType == IPNet {
		if ip := net.ParseIP(str); ip != nil {
			switch h.addrType {
			case IP:
				return str == h.addr
			case IPNet:
				return h.ipnet.Contains(ip)
			}
		} else {
			return false
		}
	}
	if h.addrType == DomainName {
		return h.addr == str
	} else {
		return h.reg.MatchString(str)
	}
}
